package org.eiahe.hr.attendance.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.github.linpeilie.Converter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.ehe.system.service.IHrEmployeeService;
import org.eiahe.hr.attendance.domain.*;
import org.eiahe.hr.attendance.domain.bo.OvertimeRecordBo;
import org.eiahe.hr.attendance.domain.vo.OvertimeRecordVo;
import org.eiahe.hr.attendance.mapper.AttendanceRecordMapper;
import org.eiahe.hr.attendance.mapper.EmployeeShiftMapper;
import org.eiahe.hr.attendance.mapper.HolidayMapper;
import org.eiahe.hr.attendance.mapper.OvertimeRecordMapper;
import org.eiahe.hr.attendance.mapper.OvertimeRuleMapper;
import org.eiahe.hr.attendance.mapper.ShiftMapper;
import org.eiahe.hr.attendance.service.IOvertimeRecordService;
import org.ehe.common.mybatis.core.page.PageQuery;
import org.ehe.common.mybatis.core.page.TableDataInfo;
import org.ehe.system.domain.HrEmployee;
import org.ehe.system.mapper.HrEmployeeMapper;
import org.eiahe.hr.filler.EmployeeNameFiller;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 加班记录Service业务层处理
 *
 * @author zhangnn
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class OvertimeRecordServiceImpl implements IOvertimeRecordService {

    private final OvertimeRecordMapper baseMapper;
    private final AttendanceRecordMapper attendanceRecordMapper;
    private final IHrEmployeeService employeeService;
    private final HrEmployeeMapper employeeMapper;
    private final EmployeeShiftMapper employeeShiftMapper;
    private final ShiftMapper shiftMapper;
    private final HolidayMapper holidayMapper;
    private final OvertimeRuleMapper overtimeRuleMapper;
    private final Converter converter;
    private final EmployeeNameFiller employeeNameFiller;

    /**
     * 加班类型映射
     */
    private static final Map<String, String> OVERTIME_TYPE_MAP = new HashMap<>();

    /**
     * 来源映射
     */
    private static final Map<String, String> SOURCE_MAP = new HashMap<>();

    /**
     * 审批状态映射
     */
    private static final Map<String, String> APPROVAL_STATUS_MAP = new HashMap<>();

    static {
        OVERTIME_TYPE_MAP.put("1", "工作日");
        OVERTIME_TYPE_MAP.put("2", "周末");
        OVERTIME_TYPE_MAP.put("3", "节假日");

        SOURCE_MAP.put("AUTO", "自动计算");
        SOURCE_MAP.put("MANUAL", "手动申请");

        APPROVAL_STATUS_MAP.put("0", "待审批");
        APPROVAL_STATUS_MAP.put("1", "审批中");
        APPROVAL_STATUS_MAP.put("2", "已拒绝");
        APPROVAL_STATUS_MAP.put("3", "已通过");
    }

    @Override
    public OvertimeRecordVo queryById(Long recordId) {
        OvertimeRecordVo vo = baseMapper.selectVoById(recordId);
        if (vo != null) {
            enrichVo(vo);
        }
        return vo;
    }

    @Override
    public TableDataInfo<OvertimeRecordVo> queryPageList(OvertimeRecordBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<OvertimeRecord> lqw = buildQueryWrapper(bo);
        Page<OvertimeRecordVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        result.getRecords().forEach(this::enrichVo);
        return TableDataInfo.build(result);
    }

    @Override
    public List<OvertimeRecordVo> queryList(OvertimeRecordBo bo) {
        LambdaQueryWrapper<OvertimeRecord> lqw = buildQueryWrapper(bo);
        List<OvertimeRecordVo> list = baseMapper.selectVoList(lqw);
        list.forEach(this::enrichVo);
        return list;
    }

    private LambdaQueryWrapper<OvertimeRecord> buildQueryWrapper(OvertimeRecordBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<OvertimeRecord> lqw = Wrappers.lambdaQuery();
        lqw.eq(StringUtils.isNotBlank(bo.getEmployeeNo()), OvertimeRecord::getEmployeeNo, bo.getEmployeeNo());
        if (StringUtils.isNotBlank(bo.getEmployeeName())) {
            List<String> employeeNos = employeeService.queryByEmployeeName(bo.getEmployeeName());
            if (CollUtil.isEmpty(employeeNos)) {
                lqw.eq(OvertimeRecord::getEmployeeNo, -1);
            } else {
                lqw.in(OvertimeRecord::getEmployeeNo, employeeNos);
            }
        }
        lqw.eq(bo.getUserId() != null, OvertimeRecord::getUserId, bo.getUserId());
        lqw.eq(bo.getOvertimeDate() != null, OvertimeRecord::getOvertimeDate, bo.getOvertimeDate());
        lqw.eq(StringUtils.isNotBlank(bo.getOvertimeType()), OvertimeRecord::getOvertimeType, bo.getOvertimeType());
        lqw.eq(StringUtils.isNotBlank(bo.getSource()), OvertimeRecord::getSource, bo.getSource());
        lqw.eq(StringUtils.isNotBlank(bo.getApprovalStatus()), OvertimeRecord::getApprovalStatus,
                bo.getApprovalStatus());
        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), OvertimeRecord::getStatus, bo.getStatus());

        // 日期范围查询
        if (params.get("beginOvertimeDate") != null && params.get("endOvertimeDate") != null) {
            lqw.between(OvertimeRecord::getOvertimeDate,
                    params.get("beginOvertimeDate"), params.get("endOvertimeDate"));
        }

        lqw.orderByDesc(OvertimeRecord::getOvertimeDate);
        return lqw;
    }

    /**
     * 丰富VO数据
     */
    private void enrichVo(OvertimeRecordVo vo) {
        // 填充员工姓名
        employeeNameFiller.fillEmployeeName(vo,OvertimeRecordVo::getEmployeeNo,OvertimeRecordVo::setEmployeeName);
        // 填充类型名称
        vo.setOvertimeTypeName(OVERTIME_TYPE_MAP.get(vo.getOvertimeType()));
        vo.setSourceName(SOURCE_MAP.get(vo.getSource()));
        vo.setApprovalStatusName(APPROVAL_STATUS_MAP.get(vo.getApprovalStatus()));

        // 计算小时数
        if (vo.getOvertimeMinutes() != null) {
            vo.setOvertimeHours(vo.getOvertimeMinutes() / 60.0);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean insertByBo(OvertimeRecordBo bo) {
        // 校验时间合法性
        validateOvertimeTime(bo);
        if(null != bo.getRecordId()){
            return updateByBo(bo);
        }

        OvertimeRecord add = converter.convert(bo, OvertimeRecord.class);
        // 手动申请默认待审批
        if (StringUtils.isBlank(add.getSource())) {
            add.setSource("MANUAL");
        }
        if (StringUtils.isBlank(add.getApprovalStatus())) {
            add.setApprovalStatus("0"); // 待审批
        }

        // 计算加班时长
        if (add.getOvertimeMinutes() == null || add.getOvertimeMinutes() == 0) {
            int minutes = calculateOvertimeMinutes(add);
            add.setOvertimeMinutes(minutes);
        }

        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setRecordId(add.getRecordId());
        }
        return flag;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateByBo(OvertimeRecordBo bo) {
        validateOvertimeTime(bo);

        OvertimeRecord update = converter.convert(bo, OvertimeRecord.class);

        // 重新计算加班时长
        if (update.getStartTime() != null && update.getEndTime() != null) {
            int minutes = calculateOvertimeMinutes(update);
            update.setOvertimeMinutes(minutes);
        }

        return baseMapper.updateById(update) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteWithValidByIds(Collection<Long> ids) {
        return baseMapper.deleteByIds(ids) > 0;
    }

    /**
     * 校验加班时间合法性
     */
    private void validateOvertimeTime(OvertimeRecordBo bo) {
        if (bo.getStartTime() != null && bo.getEndTime() != null) {
            if (bo.getStartTime().isAfter(bo.getEndTime())) {
                throw new RuntimeException("开始时间不能晚于结束时间");
            }
        }
    }

    /**
     * 计算加班时长(分钟)
     */
    private int calculateOvertimeMinutes(OvertimeRecord record) {
        if (record.getStartTime() == null || record.getEndTime() == null) {
            return 0;
        }

        // 计算总分钟数
        Duration duration = Duration.between(record.getStartTime(), record.getEndTime());
        long totalMinutes = duration.toMinutes();

        // 扣除午休时间(12:00-13:00)
        LocalTime lunchStart = LocalTime.of(12, 0);
        LocalTime lunchEnd = LocalTime.of(13, 0);

        LocalTime startTime = record.getStartTime();
        LocalTime endTime = record.getEndTime();

        // 判断是否跨越午休时间
        if ((startTime.isBefore(lunchEnd) && endTime.isAfter(lunchStart))) {
            // 跨越午休,扣除60分钟
            totalMinutes -= 60;
        }

        return (int) Math.max(0, totalMinutes);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void autoCalculateOvertime(String employeeNo, LocalDate attendanceDate) {
        log.info("自动计算加班记录:工号={},日期={}", employeeNo, attendanceDate);

        try {
            // 获取员工信息
            HrEmployee employee = employeeMapper.selectOne(
                    Wrappers.lambdaQuery(HrEmployee.class)
                            .eq(HrEmployee::getEmployeeNo, employeeNo)
                            .last("LIMIT 1"));
            if (employee == null) {
                log.warn("员工不存在:工号={}", employeeNo);
                return;
            }

            // 获取员工班次
            EmployeeShift employeeShift = employeeShiftMapper.selectOne(
                    Wrappers.lambdaQuery(EmployeeShift.class)
                            .eq(EmployeeShift::getEmployeeNo, employeeNo)
                            .le(EmployeeShift::getEffectiveDate, attendanceDate)
                            .and(wrapper -> wrapper.isNull(EmployeeShift::getExpiryDate)
                                    .or().ge(EmployeeShift::getExpiryDate, attendanceDate))
                            .orderByDesc(EmployeeShift::getEffectiveDate)
                            .last("LIMIT 1"));

            if (employeeShift == null) {
                log.warn("员工未配置班次:工号={}", employeeNo);
                return;
            }

            // 获取班次信息
            Shift shift = shiftMapper.selectById(employeeShift.getShiftId());
            if (shift == null) {
                log.warn("班次不存在:ID={}", employeeShift.getShiftId());
                return;
            }

            // 获取加班规则
            OvertimeRule overtimeRule = overtimeRuleMapper.selectOne(
                    Wrappers.lambdaQuery(OvertimeRule.class)
                            .eq(OvertimeRule::getShiftId, shift.getShiftId())
                            .eq(OvertimeRule::getStatus, "0")
                            .last("LIMIT 1"));

            if (overtimeRule == null) {
                log.warn("班次未配置加班规则:班次ID={}", shift.getShiftId());
                return;
            }

            // 判断是否为节假日/周末
            String overtimeType = determineOvertimeType(attendanceDate);

            // 获取当天的考勤记录
            List<AttendanceRecord> attendanceRecords = attendanceRecordMapper.selectList(
                    Wrappers.lambdaQuery(AttendanceRecord.class)
                            .eq(AttendanceRecord::getEmployeeNo, employeeNo)
                            .eq(AttendanceRecord::getAttendanceDate, attendanceDate)
                            .orderByAsc(AttendanceRecord::getClockTime));

            if (attendanceRecords.isEmpty()) {
                log.info("当天无考勤记录,无需计算加班");
                return;
            }

            // 根据加班类型计算加班时长
            Integer overtimeMinutes = null;
            LocalDateTime overtimeStart = null;
            LocalDateTime overtimeEnd = null;

            if ("1".equals(overtimeType)) {
                // 工作日:判断下班后加班
                overtimeMinutes = calculateWorkdayOvertime(attendanceRecords, shift, overtimeRule);
                if (overtimeMinutes != null && overtimeMinutes > 0) {
                    // 找出最后一次下班打卡
                    for (int i = attendanceRecords.size() - 1; i >= 0; i--) {
                        AttendanceRecord record = attendanceRecords.get(i);
                        if ("2".equals(record.getClockType())) {
                            overtimeEnd = attendanceDate.atTime(record.getClockTime());
                            // 计算加班开始时间 = 下班时间 + 阈值
                            overtimeStart = attendanceDate.atTime(shift.getWorkEndTime())
                                    .plusMinutes(overtimeRule.getWorkdayThresholdMinutes());
                            break;
                        }
                    }
                }
            } else if ("2".equals(overtimeType) || "3".equals(overtimeType)) {
                // 周末/节假日:有打卡即算加班
                if (("1".equals(overtimeRule.getWeekendRule()) && "2".equals(overtimeType)) ||
                        ("1".equals(overtimeRule.getHolidayRule()) && "3".equals(overtimeType))) {

                    overtimeMinutes = calculateWeekendHolidayOvertime(attendanceRecords);
                    if (overtimeMinutes != null && overtimeMinutes > 0) {
                        // 使用第一次和最后一次打卡时间
                        overtimeStart = attendanceDate.atTime(attendanceRecords.get(0).getClockTime());
                        overtimeEnd = attendanceDate.atTime(
                                attendanceRecords.get(attendanceRecords.size() - 1).getClockTime());
                    }
                }
            }

            // 创建加班记录
            if (overtimeMinutes != null && overtimeMinutes > 0) {
                // 按最小单位取整
                int minUnit = overtimeRule.getMinUnitMinutes();
                overtimeMinutes = (int) (Math.ceil(overtimeMinutes / (double) minUnit) * minUnit);

                // 检查是否已存在记录
                Long existCount = baseMapper.selectCount(
                        Wrappers.lambdaQuery(OvertimeRecord.class)
                                .eq(OvertimeRecord::getEmployeeNo, employeeNo)
                                .eq(OvertimeRecord::getOvertimeDate, attendanceDate)
                                .eq(OvertimeRecord::getSource, "AUTO"));

                if (existCount == null || existCount == 0) {
                    OvertimeRecord overtimeRecord = new OvertimeRecord();
                    overtimeRecord.setEmployeeNo(employeeNo);
                    overtimeRecord.setUserId(employee.getUserId());
                    overtimeRecord.setOvertimeDate(attendanceDate);
                    overtimeRecord.setStartTime(overtimeStart.toLocalTime());
                    overtimeRecord.setEndTime(overtimeEnd.toLocalTime());
                    overtimeRecord.setOvertimeMinutes(overtimeMinutes);
                    overtimeRecord.setOvertimeType(overtimeType);
                    overtimeRecord.setSource("AUTO");
                    overtimeRecord.setApprovalStatus("3"); // 自动计算的直接通过
                    overtimeRecord.setStatus("0");

                    baseMapper.insert(overtimeRecord);
                    log.info("创建加班记录成功:工号={},日期={},时长={}分钟",
                            employeeNo, attendanceDate, overtimeMinutes);
                }
            }

        } catch (Exception e) {
            log.error("自动计算加班记录失败:工号={},日期={}", employeeNo, attendanceDate, e);
            throw new RuntimeException("自动计算加班记录失败", e);
        }
    }

    /**
     * 判断加班类型
     */
    private String determineOvertimeType(LocalDate date) {
        // 查询是否为节假日
        Holiday holiday = holidayMapper.selectOne(
                Wrappers.lambdaQuery(Holiday.class)
                        .eq(Holiday::getHolidayDate, date)
                        .eq(Holiday::getStatus, "0")
                        .last("LIMIT 1"));

        if (holiday != null) {
            // 是节假日
            return "3";
        }

        // 判断是否为周末
        int dayOfWeek = date.getDayOfWeek().getValue();
        if (dayOfWeek == 6 || dayOfWeek == 7) {
            // 是周末
            return "2";
        }

        // 工作日
        return "1";
    }

    /**
     * 计算工作日加班时长
     */
    private Integer calculateWorkdayOvertime(List<AttendanceRecord> records,
            Shift shift, OvertimeRule rule) {
        // 找出最后一次下班打卡
        AttendanceRecord lastOffDuty = null;
        for (int i = records.size() - 1; i >= 0; i--) {
            AttendanceRecord record = records.get(i);
            if ("2".equals(record.getClockType())) {
                lastOffDuty = record;
                break;
            }
        }

        if (lastOffDuty == null) {
            return null;
        }

        // 计算下班时间 + 阈值
        LocalTime thresholdTime = shift.getWorkEndTime()
                .plusMinutes(rule.getWorkdayThresholdMinutes());

        // 判断是否超过阈值
        if (lastOffDuty.getClockTime().isAfter(thresholdTime)) {
            // 计算加班时长 = 最后打卡时间 - (下班时间 + 阈值)
            Duration duration = Duration.between(thresholdTime, lastOffDuty.getClockTime());
            return (int) duration.toMinutes();
        }

        return null;
    }

    /**
     * 计算周末/节假日加班时长
     */
    private Integer calculateWeekendHolidayOvertime(List<AttendanceRecord> records) {
        if (records.size() < 2) {
            return null;
        }

        // 使用第一次和最后一次打卡计算
        LocalTime firstClock = records.get(0).getClockTime();
        LocalTime lastClock = records.get(records.size() - 1).getClockTime();

        Duration duration = Duration.between(firstClock, lastClock);
        long minutes = duration.toMinutes();

        // 扣除午休(12:00-13:00)
        LocalTime lunchStart = LocalTime.of(12, 0);
        LocalTime lunchEnd = LocalTime.of(13, 0);

        if (firstClock.isBefore(lunchEnd) && lastClock.isAfter(lunchStart)) {
            minutes -= 60;
        }

        return (int) Math.max(0, minutes);
    }

    /**
     * 30天内加班异常
     * @param bo
     * @param pageQuery
     * @return
     */
    public TableDataInfo<OvertimeRecordVo> listMonth(OvertimeRecordBo bo, PageQuery pageQuery) {
        if(StringUtils.isBlank(bo.getEmployeeNo())){
            return new TableDataInfo<>();
        }
        LambdaQueryWrapper<OvertimeRecord> lqw = new LambdaQueryWrapper<>();
        lqw.eq(OvertimeRecord::getEmployeeNo,bo.getEmployeeNo());
        //lqw.ne(AttendanceRecord::getAttendanceStatus,0);//异常
        lqw.between(OvertimeRecord::getOvertimeDate,LocalDate.now().minusDays(30),LocalDate.now());

        Page<OvertimeRecordVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        result.getRecords().forEach(this::enrichVo);
        return TableDataInfo.build(result);
    }
}
